/*  NickBan
cvars:
1 - disconnect
2 - exit
3 - kick powod z amx_nickban_error
4 - symulacja bledu (disconnect) amx_nickban_error wypisane w konsoli
5 - nic

amx_nickban_action_ban (1) - akcja po zbanowainu gracza
	
amx_nickban_action_connect (1) - akcja uykonywana jesli zbanowany gracz sie polaczy

amx_nickban_nodamage (1) - zbanowany gracz nie zadaje obrazen

amx_nickban_error ("Server Fatal Error. Connection with #IP terminated) - blad przy symulacji bledu

#IP - zamieniane jest na IP gracza
#NAME - zamieniane jest na nick gracza


komendy:
	amx_nickbanmenu - menu z komendami admina

*/

#include <amxmodx>
#include <amxmisc>
#include <hamsandwich>

#define PLUGIN "Nick Ban"
#define VERSION "1.0"
#define AUTHOR "Zabijaka"
#define LEN_NAME 32
#define LEN_FILE 128
#define IsPlayer(%1) (1<= %1 <= gMaxPlayers)
#define MAX_PLAYERS 33

new gCvarActive;
new gCvarActionBan;
new gCvarActionConnect;
new gCvarNoDamage;
new gCvarError;

new PREFIX[]="[NICKBANS]";
new LOG_FILE []= "nickbans.log";

enum _:FILE_TYPE {
	FT_SAFE=1,
	FT_EQUAL,
	FT_ENDS_WITH,
	FT_STARTS_WITH,
	FT_CONTAINS
}
enum _:MENU_TYPE{
	MT_ADD_SAFE =1,
	MT_BAN_EQUALS,
	MT_BAN_ENDS_WITH,
	MT_BAN_STARTS_WITH,
	MT_BAN_CONTAINS,
	MT_BAN_PLAYER,
	MT_BAN_DISCONNECTED_PLAYER,
	MT_UNBAN_EQUALS,
	MT_UNBAN_ENDS_WITH,
	MT_UNBAN_STARTS_WITH,
	MT_UNBAN_CONTAINS,
	MT_REMOVE_SAFE
}

new Array:gNickArrays[FILE_TYPE];
new gNickArraySize[FILE_TYPE];
new gFileNames[FILE_TYPE][LEN_FILE];
new gMaxPlayers;
new Array:gDisconnectedPlayers;
new gDisconnectedPlayersSize;
new bool:gBanned[MAX_PLAYERS];
new gType[MAX_PLAYERS];
new typeName[FILE_TYPE][]={
	"",
	"Wyjatki",
	"Zgodne w calosci",
	"Zgodny koniec",
	"Zgodny poczatek",
	"Zawierajace tekst"
}
public client_infochanged(id){
	new infoName[LEN_NAME];
	new name[LEN_NAME];
	if(is_user_connected(id) && !gBanned[id]){
		get_user_info(id, "name", infoName, LEN_NAME-1);
		get_user_name(id, name, LEN_NAME-1);
		if(!equali(name, infoName)){
			if(checkNameBanned(infoName)){
				log_to_file(LOG_FILE, "Wykryto zbnaowany nick %s", infoName);
				processBan(id, true);
			}
		}
	}
}


public plugin_init() {
	register_plugin(PLUGIN, VERSION, AUTHOR);
	gMaxPlayers = get_maxplayers();
	
	gCvarActive = register_cvar("amx_nickban_active" , "1");
	gCvarActionBan  = register_cvar("amx_nickban_action_ban", "1");
	gCvarActionConnect  = register_cvar("amx_nickban_action_connect", "1");
	gCvarError = register_cvar("amx_nickban_error", "Server Fatal Error. Connection with #IP terminated");
	gCvarNoDamage = register_cvar("amx_nickban_nodamage", "1");
	
	register_clcmd("amx_nickbanmenu", "adminNickBanMenu", ADMIN_BAN);
	register_clcmd("amx_nickban_nick", "banNickCommand", ADMIN_BAN);
	
	RegisterHam(Ham_TakeDamage, "player", "fwd_Ham_TakeDamage");
	
	new cfgDir[128];
	get_configsdir(cfgDir, charsmax(cfgDir));
	
	format(gFileNames[FT_SAFE], LEN_FILE-1, "%s\nickbans\safe.ini", cfgDir);
	format(gFileNames[FT_EQUAL], LEN_FILE-1, "%s\nickbans\equal.ini", cfgDir);
	format(gFileNames[FT_ENDS_WITH], LEN_FILE-1, "%s\nickbans\ends_with.ini", cfgDir);
	format(gFileNames[FT_STARTS_WITH], LEN_FILE-1, "%s\nickbans\starts_with.ini", cfgDir);
	format(gFileNames[FT_CONTAINS], LEN_FILE-1, "%s\nickbans\contains.ini", cfgDir);
	
	gDisconnectedPlayers = ArrayCreate(LEN_NAME);
	
	for(new type=FT_SAFE;type<=FT_CONTAINS;type++)
		readFile(type);
	
}

public fwd_Ham_TakeDamage(id, idinflictor, idattacker, Float:damage, damagebits){
	if(get_pcvar_num(gCvarNoDamage) && get_pcvar_num(gCvarActive)){
		if(IsPlayer(idattacker) && gBanned[idattacker]){
			SetHamParamFloat(4, 0.0);
		}
	}
	return HAM_IGNORED;
}

public banNickCommand(id, level,cid) {
	if(!get_pcvar_num(gCvarActive))
		return PLUGIN_CONTINUE;
	if(!cmd_access(id, level, cid, 2)){
		return PLUGIN_HANDLED;
	}
	new arg[LEN_NAME];
	read_argv(1, arg, LEN_NAME-1);
	remove_quotes(arg);
	trim(arg);
	if(arg[0] && gType[id]){
		addNick(id, gType[id], arg);
	}
	gType[id] = 0;
	return PLUGIN_HANDLED;
}

public client_disconnect(id){
	new name[LEN_NAME];
	get_user_name(id, name, LEN_NAME-1);
	if(!arrayContains(gDisconnectedPlayers, gDisconnectedPlayersSize, name)){
		ArrayPushString(gDisconnectedPlayers, name);
		gDisconnectedPlayersSize++;
	}
}

readFile(type) {
	new Array: array =   ArrayCreate(LEN_NAME);
	new  size = 0;
	new file = fopen(gFileNames[type], "r");
	if(file){
		new name[LEN_NAME];
		new bool:isFirstLine=true;
		while(fgets(file, name, LEN_NAME-1)){
			trim(name);
			if(!name[0]  || (name[0] == ';' && isFirstLine)){		
				isFirstLine = false;
				continue;
			}
			isFirstLine = false;
			ArrayPushString(array, name);
			size++;
		}
		fclose(file);
	}
	gNickArrays[type] = array;
	gNickArraySize[type] = size;
}
writeFile(type){
	new file = fopen(gFileNames[type], "wt") ;
	
	 if(!file){
	 	log_amx("%s - Nie mozna zapisac pliku %s", PREFIX, gFileNames[type]);
	 	return;
	}
	switch(type) {
		case FT_SAFE:  fprintf(file,"%s^n",  "; Gracz z tym nickiem nie bedzie banowany");
		case FT_ENDS_WITH: fprintf(file, "%s^n","; Kazdy gracz ktorego nick konczy sie danym ciagiem znakow bedzie zbanowany");
		case FT_STARTS_WITH: fprintf(file, "%s^n","; Kazdy gracz ktorego nick zaczyna sie danym ciagiem znakow bedzie zbanowany");
		case FT_CONTAINS: fprintf(file, "%s^n","; Kazdy gracz ktorego nick zawiera dany ciag znakow bedzie zbanowany");
		case FT_EQUAL: fprintf(file, "%s^n","; Kazdy gracz ktorego nick jest taki sam jak ciag znakow bedzie zbanowany");
	}
	new size = gNickArraySize[type];
	new Array:array = gNickArrays[type];
	new name[LEN_NAME];
	
	for(new i=0;i<size;i++){
		ArrayGetString(array, i, name, LEN_NAME-1);
		fprintf(file, "%s^n",name);
	}
	fclose(file);
}

public adminNickBanMenu(id, level, cid){
	if(!get_pcvar_num(gCvarActive))
		return PLUGIN_CONTINUE;
		
	if(!cmd_access(id, level, cid, 1)){
		return PLUGIN_CONTINUE;
	}
	new menu = menu_create("Nick Bans Menu", "adminNickBanMenuH");
	new info[1];
	
	info[0] = MT_BAN_PLAYER;
	menu_additem(menu, "Zbanuj gracza", info);
	
	info[0] = MT_BAN_DISCONNECTED_PLAYER;
	menu_additem(menu, "Zbanuj gracza (rozlaczonego)", info);
	
	info[0] = MT_BAN_EQUALS;
	menu_additem(menu, "Zbanuj nick (Zgodny w calosci)", info);
	
	info[0] = MT_BAN_STARTS_WITH;
	menu_additem(menu, "Zbanuj nicki (Zgodny poczatek)", info);
	
	info[0] = MT_BAN_ENDS_WITH;
	menu_additem(menu, "Zbanuj nicki (Zgodny koniec)", info);
	
	info[0] = MT_BAN_CONTAINS;
	menu_additem(menu, "Zbanuj nicki (Zawierajace tekst)", info);
	
	
	info[0] = MT_UNBAN_EQUALS;
	menu_additem(menu, "Odbanuj nick (Zgodny w calosci)", info);
	
	info[0] = MT_UNBAN_STARTS_WITH;
	menu_additem(menu, "Odbanuj nicki (Zgodny poczatek)", info);
	
	info[0] = MT_UNBAN_ENDS_WITH;
	menu_additem(menu, "Odbanuj nicki (Zgodny koniec)", info);
	
	info[0] = MT_UNBAN_CONTAINS;
	menu_additem(menu, "Odbanuj nicki (Zawierajace tekst)", info);
	
	info[0] = MT_ADD_SAFE;
	menu_additem(menu, "Dodaj wyjatek (Nick nie bedzie banowany)", info);
	
	info[0] = MT_REMOVE_SAFE;
	menu_additem(menu, "Usun wyjatek", info);
	
	return PLUGIN_HANDLED;
}

public adminNickBanMenuH(id, menu, item){
	if(item == MENU_EXIT){
		menu_destroy(menu);
		return PLUGIN_HANDLED;
	}
	new info[1];
	new acc, call;
	new name[64];
	menu_item_getinfo(menu, item, acc, info, 1, name,63, call);
	
	switch(info[0]){
		case MT_BAN_PLAYER: banPlayerMenu(id);
		case MT_BAN_DISCONNECTED_PLAYER: showArrayNickMenu(id, gDisconnectedPlayers, gDisconnectedPlayersSize, "Zbanuj nick", "banPlayerMenuH");
		
		case MT_BAN_EQUALS:  showAddNickCommand(id, FT_EQUAL);
		case MT_BAN_CONTAINS:  showAddNickCommand(id, FT_CONTAINS);
		case MT_BAN_STARTS_WITH:  showAddNickCommand(id, FT_STARTS_WITH);
		case MT_BAN_ENDS_WITH:  showAddNickCommand(id, FT_ENDS_WITH);
		case MT_ADD_SAFE:  showAddNickCommand(id, FT_SAFE);
		
		case MT_REMOVE_SAFE:  showArrayNickMenu2(id, FT_SAFE, name, "delNickSafeH");
		case MT_UNBAN_EQUALS:  showArrayNickMenu2(id, FT_EQUAL, name, "delNickEqualH");
		case MT_UNBAN_ENDS_WITH:  showArrayNickMenu2(id, FT_ENDS_WITH, name, "delNickEndsWithH");
		case MT_UNBAN_STARTS_WITH:  showArrayNickMenu2(id, FT_STARTS_WITH, name, "delNickStartsWithH");
		case MT_UNBAN_CONTAINS:  showArrayNickMenu2(id, FT_CONTAINS, name, "delNickContainsH");
	}
	return PLUGIN_HANDLED;
}

public delNickSafeH(id, menu, item){
	removeNickFromArray(id, FT_SAFE, menu, item);
}
public delNickEqualH(id, menu, item){
	removeNickFromArray(id, FT_EQUAL, menu, item);
}
public delNickEndsWithH(id, menu, item){
	removeNickFromArray(id, FT_ENDS_WITH, menu, item);
}
public delNickStartsWithH(id, menu, item){
	removeNickFromArray(id, FT_STARTS_WITH, menu, item);
}
public delNickContainsH(id, menu, item){
	removeNickFromArray(id, FT_CONTAINS, menu, item);
}

removeNickFromArray(admin, type, menu, item){
	if(item == MENU_EXIT){
		menu_destroy(menu);
		return PLUGIN_HANDLED;
	}
	new info[1];
	new acc, call;
	new name[LEN_NAME];
	menu_item_getinfo(menu, item, acc, info, 0, name, LEN_NAME , call);
	if(arrayRemove(gNickArrays[type], gNickArraySize[type], name)){
		client_print(admin, print_chat, "%s %s zostal usuniety ze zbioru", PREFIX, name);
		new adminName[LEN_NAME];
		get_user_name(admin, adminName, LEN_NAME-1);
		log_to_file(LOG_FILE, "Admin %s odbanowal nick %s [Typ: %s]", adminName, name, typeName[type]);
		gNickArraySize[type]--;
		writeFile(type);
	}
	menu_destroy(menu);
	return PLUGIN_HANDLED;
}

showAddNickCommand(id, type){
	gType[id] = type;
	client_cmd(id, "messagemode amx_nickban_nick");
}
showArrayNickMenu2(id, type, const menuName[], const menuHandler[]){
	showArrayNickMenu(id, gNickArrays[type], gNickArraySize[type], menuName, menuHandler);
}
showArrayNickMenu(id, Array:array, size, const menuName[], const menuHandler[]){
	new name[LEN_NAME];
	new menu = menu_create(menuName, menuHandler);
	for(new i=0;i<size;i++){
		ArrayGetString(array, i, name, LEN_NAME-1);
		menu_additem(menu, name);
	}
	menu_display(id, menu);
}

banPlayerMenu(id){
	new player, num;
	new players[32];
	new name[LEN_NAME];
	new menu = menu_create("Zbanuj Nick Gracza", "banPlayerMenuH");
	get_players(players, num, "ch");
	new info[1];
	for(new i=0;i<num;i++){
		player = players[i];
		info[0] = player;
		get_user_name(player, name, LEN_NAME -1);
		menu_additem(menu, name, info);
	}
	menu_display(id, menu);
}


public banPlayerMenuH(id, menu, item){
	if(item == MENU_EXIT){
		menu_destroy(menu);
		return PLUGIN_HANDLED;
	}
	new info[1];
	new acc, call;
	new name[LEN_NAME];
	menu_item_getinfo(menu, item, acc, info, 1, name, LEN_NAME, call);
	if(addNick(id, FT_EQUAL, name)){
		processBan(info[0], false);
	} 
	menu_destroy(menu);
	return PLUGIN_HANDLED;
}

processBan(id, bool:onConnect){
	if(IsPlayer(id) && is_user_connected(id) && !gBanned[id]){
		gBanned[id] = true;
		new action;
		if(onConnect)
			action = get_pcvar_num(gCvarActionConnect);
		else
			action = get_pcvar_num(gCvarActionBan);
			
		switch(action){
			case 1: client_cmd(id, "disconnect");
			case 2: client_cmd(id, "exit");
			case 3: {
				new error[128];
				get_error(id,error, charsmax(error));
				server_cmd("kick #%d ^"%s^"", get_user_userid(id), error);
			}
			case 4: {
				new error[128];
				get_error(id,error, charsmax(error));
				client_print(id, print_console, error);
				set_task(0.05, "delayedKick", id);
			}
		}
	}
}

public delayedKick(id){
	if(IsPlayer(id) && is_user_connected(id)){
		client_cmd(id, "disconnect");
	}
}
bool:arrayContains(Array:array, size, const name[LEN_NAME]){
	new output[LEN_NAME];
	for(new i=0;i<size;i++){
		ArrayGetString(array, i, output, LEN_NAME-1);
		if(equali(name, output))
			return true;
	}
	return false;
}


bool:arrayContains2(type, const name[LEN_NAME]){
	return arrayContains(gNickArrays[type], gNickArraySize[type], name);
}
bool:addNick(admin, type, const name[LEN_NAME]){
	if(arrayContains2(type,  name)){
		client_print(admin, print_chat, "%s Nick %s jest juz w zbiorze.", PREFIX, name);
		return false;
	}
	ArrayPushString(gNickArrays[type], name);
	write_file(gFileNames[type], name);
	gNickArraySize[type]++;
	client_print(admin, print_chat, "%s Nick %s dodany do zbioru.", PREFIX, name);
	new adminName[LEN_NAME];
	get_user_name(admin, adminName, LEN_NAME-1);
	log_to_file(LOG_FILE, "Admin %s zbanowal nick %s [Typ: %s]", adminName, name, typeName[type]);
	return true;
}
bool:arrayRemove(Array:array, size, const name[LEN_NAME]){
	new output[LEN_NAME];
	for(new i=0;i<size;i++){
		ArrayGetString(array, i, output, LEN_NAME-1);
		if(equali(name, output)){
			ArrayDeleteItem(array, i);
			return true;
		}
	}
	return false;
}

bool:checkNameBanned(const name[LEN_NAME]){
	if(name[0] == 0)
		return false;
	if(arrayContains2(FT_SAFE, name))
		return false;
		
	if(arrayContains2(FT_EQUAL, name))
		return true;
		
	new i=0;
	
	new type=FT_CONTAINS;
	new size = gNickArraySize[type];
	new Array:array = gNickArrays[type];
	new output[LEN_NAME];
	for(i=0;i<size;i++){
		ArrayGetString(array, i, output, LEN_NAME-1);
		if(containi(name, output))
			return true;
	}
	
	type =FT_STARTS_WITH;
	size = gNickArraySize[type];
	array = gNickArrays[type];
	for(i=0;i<size;i++){
		ArrayGetString(array, i, output, LEN_NAME-1);
		if(strfind(name, output, 1) == 0)
			return true;
	}
	
	type =FT_ENDS_WITH;
	size = gNickArraySize[type];
	array = gNickArrays[type]
	new namelen = strlen(name);
	new outputlen;
	for(i=0;i<size;i++){
		ArrayGetString(array, i, output, LEN_NAME-1);
		outputlen=strlen(output);
		if(strfind(name, output, 1) == namelen-outputlen)
			return true;
	}
	return false;
}

get_error(id, error[], len){
	get_pcvar_string(gCvarError, error, len);
	if(containi(error, "#NAME")){
		new name[LEN_NAME];
		get_user_name(id, name, LEN_NAME-1);
		replace_all(error, len, "#NAME", name);
	}
	if(containi(error, "#IP")){
		new ip[16];
		get_user_ip(id, ip,15, 1);
		replace_all(error, len, "#IP", ip);
	}
}
